/**
 * Copyright 2013 Jim Burton.
 *
 * Licensed under the MIT license (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *    http://opensource.org/licenses/mit-license.php
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.google.bitcoin.core;
import com.google.bitcoin.crypto.KeyCrypter;
import com.google.bitcoin.crypto.KeyCrypterException;
import com.google.bitcoin.crypto.EncryptedPrivateKey;


import java.io.Serializable;
import java.lang.reflect.Array;
import java.security.SecureRandom;
import java.util.Arrays;

import org.bitcoinj.wallet.Protos;
import org.bitcoinj.wallet.Protos.Wallet.EncryptionType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongycastle.crypto.BufferedBlockCipher;
import org.spongycastle.crypto.engines.AESFastEngine;
import org.spongycastle.crypto.modes.CBCBlockCipher;
import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher;
import org.spongycastle.crypto.params.KeyParameter;
import org.spongycastle.crypto.params.ParametersWithIV;

import com.google.common.base.Preconditions;
import com.google.protobuf.ByteString;
import com.lambdaworks.crypto.SCrypt;

/**
 * <p>This is a fake class that encrypts and decrypts byte arrays using very simple method
 * for just concatenating the message with a string and the key.</p>
 *
 * <p>You can use this class instead of KeyCrypterScrypt in your unit tests to make them
 *   faster.</p>
 */
public class FakeKeyCrypter implements KeyCrypter, Serializable {

    private static final String ENCRYPTED_WITH = " encrypted with ";

	public Logger log = LoggerFactory.getLogger(FakeKeyCrypter.class.getName());

    private static final long serialVersionUID = 1234123123123L;

    /**
     * Key length in bytes.
     */
    public static final int KEY_LENGTH = 32; // = 256 bits.


    /**
     * Fake Encryption/ Decryption
     */
    public FakeKeyCrypter() {
    }


    /**
     * Generate AES key.
     *
     * This is a very slow operation compared to encrypt/ decrypt so it is normally worth caching the result.
     *
     * @param password    The password to use in key generation
     * @return            The KeyParameter containing the created AES key
     * @throws            KeyCrypterException
     */
    @Override
    public KeyParameter deriveKey(CharSequence password) throws KeyCrypterException {
        byte[] passwordBytes = null;
        try {
            passwordBytes = convertToByteArray(password);
           
            byte[] keyBytes = passwordBytes;
            return new KeyParameter(keyBytes);
        } catch (Exception e) {
            throw new KeyCrypterException("Could not generate key from password and salt.", e);
        } finally {
            // Zero the password bytes.
            if (passwordBytes != null) {
                java.util.Arrays.fill(passwordBytes, (byte) 0);
            }
        }
    }

 /**
     * The size of an AES block in bytes.
     * This is also the length of the initialisation vector.
     */

    byte[] concat(byte[] A, byte[] B) {
    	   int aLen = A.length;
    	   int bLen = B.length;
    	   byte[] C= new byte[aLen+bLen];
    	   System.arraycopy(A, 0, C, 0, aLen);
    	   System.arraycopy(B, 0, C, aLen, bLen);
    	   return C;
    	}


    
    /**
     * Password based encryption using AES - CBC 256 bits.
     */
    @Override
    public EncryptedPrivateKey encrypt(byte[] plainBytes, KeyParameter aesKey) throws KeyCrypterException {
        Preconditions.checkNotNull(plainBytes);
        Preconditions.checkNotNull(aesKey);
		byte[] iv = new byte[0];
		byte[] encrypted = concat(concat(plainBytes, ENCRYPTED_WITH.getBytes()),
			aesKey.getKey());
		removeFromEnd(encrypted, aesKey.getKey());
        try {
            return new EncryptedPrivateKey(iv, encrypted);
        } catch (Exception e) {
            throw new KeyCrypterException("Could not encrypt bytes.", e);
        }
    }

    /**
     * Decrypt bytes previously encrypted with this class.
     *
     * @param privateKeyToDecode    The private key to decrypt
     * @param aesKey           The AES key to use for decryption
     * @return                 The decrypted bytes
     * @throws                 KeyCrypterException if bytes could not be decoded to a valid key
     */
    @Override
    public byte[] decrypt(EncryptedPrivateKey privateKeyToDecode, KeyParameter aesKey) throws KeyCrypterException {
        Preconditions.checkNotNull(privateKeyToDecode);
        Preconditions.checkNotNull(aesKey);
        try {
        byte[] s = privateKeyToDecode.getEncryptedBytes();
        byte[] sWithoutKey = removeFromEnd(s, aesKey.getKey());
        byte[] sWithoutKeyAndEncryptedWith =
        		removeFromEnd(sWithoutKey, ENCRYPTED_WITH.getBytes());
        
        return sWithoutKeyAndEncryptedWith;
        } catch(IllegalArgumentException e) {
        	throw new KeyCrypterException("Could not decrypt bytes", e);
        }
    }

    private byte[] removeFromEnd(byte[] s, byte[] ending) {
    	byte[] sending = new byte[ending.length];
    	System.arraycopy(s, s.length - ending.length,
    			sending, 0, ending.length);
        Preconditions.checkArgument(Arrays.equals(ending, sending));
        byte[] r = new byte[s.length - ending.length];
        System.arraycopy(s, 0,
    			r, 0, s.length - ending.length);
        return r;
        
	}


	/**
     * Convert a CharSequence (which are UTF16) into a byte array.
     *
     * Note: a String.getBytes() is not used to avoid creating a String of the password in the JVM.
     */
    private byte[] convertToByteArray(CharSequence charSequence) {
        Preconditions.checkNotNull(charSequence);

        byte[] byteArray = new byte[charSequence.length() << 1];
        for(int i = 0; i < charSequence.length(); i++) {
            int bytePosition = i << 1;
            byteArray[bytePosition] = (byte) ((charSequence.charAt(i)&0xFF00)>>8);
            byteArray[bytePosition + 1] = (byte) (charSequence.charAt(i)&0x00FF);
        }
        return byteArray;
    }

    /**
     * Return the EncryptionType enum value which denotes the type of encryption/ decryption that this KeyCrypter
     * can understand.
     */
    @Override
    public EncryptionType getUnderstoodEncryptionType() {
        return EncryptionType.FAKE_ENCRYPTED;
    }

    @Override
    public String toString() {
        return "FakeEncrypted";
    }

    @Override
    public int hashCode() {
        return com.google.common.base.Objects.hashCode(0);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        return true;
    }
}
